<?php
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'init.inc.php';
require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'supercompare.inc.php';
require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'examplequeries.inc.php';
require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'dummyschemastats.class.php';

class OptimiserTest extends PHPUnit_Framework_TestCase {
	
	var $parser;
	var $optimiser;
	var $examplequeries;
	
	protected function setUp()
    {
        $this->parser = ARC2::getSPARQLParser();
		$schemaStats = new DummySchemaStats();
		$schemaStats->add('http://example.com/hasName',50000,50000,2500);
		$schemaStats->add('http://example.com/hasParent',12000,6000,3000);
		
		$schemaStats->add('http://www.w3.org/1999/02/22-rdf-syntax-ns#type',540160,367782,25);
		$schemaStats->add('http://www.w3.org/2004/02/skos/core#prefLabel',1584,1584,1578); 
		$schemaStats->add('http://www.w3.org/2004/02/skos/core#inScheme',1584,1584,2); 
		
		$schemaStats->add('http://talisaspire.com/schema#listReferences',142999,142999,142999); 
		$schemaStats->add('http://talisaspire.com/schema#list',213127,142999,9579); 
		
		
		
		//$schemaStats = SchemaStats::getTheSchemaStats()
		$this->optimiser = new Optimiser($schemaStats);
		$this->examplequeries = new ExampleQueries();

    }
 
    protected function tearDown()
    {
        $this->parser = NULL;
    }

	var $simple_item1 = array( 'type'=>'triple', 's'=>'o2', 's_type'=>'var', 
	       					'p'=>'http://example.com/hasName', 'p_type'=>'uri',
		   					'o'=>'fred', 'o_type'=>'literal', 'o_datatype'=>'', 'o_lang'=>'' );
	var $simple_item2 = array( 'type'=>'triple', 's'=>'s', 's_type'=>'var', 
	       					'p'=>'http://example.com/hasParent', 'p_type'=>'uri',
		   					'o'=>'o2', 'o_type'=>'var', 'o_datatype'=>'', 'o_lang'=>'' );
	var $simple_item3 = array( 'type'=>'triple', 's'=>'s', 's_type'=>'var', 
	       					'p'=>'p', 'p_type'=>'var',
		   					'o'=>'o', 'o_type'=>'var', 'o_datatype'=>'', 'o_lang'=>'' );

	var $analysis_1 = array( 'type'=>'triple', 'tripleCount'=>20					,
							'vars'=> array(  'o2' => array( 'type'=>'triple', 'tripleCount'=>1) )
							);
	var $analysis_2 = array( 'type'=>'triple', 'tripleCount'=>12000,
							'vars'=> array(  's' => array( 'type'=>'triple', 'tripleCount'=>2,
															'vars'=> array(  'o2' => array( 'type'=>'triple', 'tripleCount'=>1) )
															),
											 'o2' => array( 'type'=>'triple', 'tripleCount'=>4,
															'vars'=> array(  's' => array( 'type'=>'triple', 'tripleCount'=>1) )
															)
											)
	 						);
	var $analysis_3 = array( 'type'=>'triple', 'tripleCount'=>OPT_SIZE_UNKNOWN );
	
	var $stats_ground_triple = array( 'ALL'=>0, 'CHECK'=>0, 'GROUND'=>0, 'MAY_BIND'=>0, 'WILL_BIND'=>0, 'tripleCount'=>1 );
	var $stats_1 = array( 'ALL'=>1, 'CHECK'=>0, 'GROUND'=>0, 'MAY_BIND'=>0, 'WILL_BIND'=>1, 'tripleCount'=>20 );
	var $stats_2 = array( 'ALL'=>2, 'CHECK'=>0, 'GROUND'=>0, 'MAY_BIND'=>0, 'WILL_BIND'=>2, 'tripleCount'=>12000 );
	var $stats_2_s_bound = array( 'ALL'=>1, 'CHECK'=>0, 'GROUND'=>0, 'MAY_BIND'=>0, 'WILL_BIND'=>1, 'tripleCount'=>2 );
	var $stats_2_o_bound = array( 'ALL'=>1, 'CHECK'=>0, 'GROUND'=>0, 'MAY_BIND'=>0, 'WILL_BIND'=>1, 'tripleCount'=>4 );
	var $stats_3 = array( 'ALL'=>3, 'CHECK'=>0, 'GROUND'=>0, 'MAY_BIND'=>0, 'WILL_BIND'=>3, 'tripleCount'=>OPT_SIZE_UNKNOWN );
	var $stats_3_s_bound = array( 'ALL'=>2, 'CHECK'=>0, 'GROUND'=>0, 'MAY_BIND'=>0, 'WILL_BIND'=>2, 'tripleCount'=>OPT_SIZE_UNKNOWN );
	
	
	//$schemaStats->add('http://example.com/hasName',50000,50000,2500);
	//$schemaStats->add('http://example.com/hasParent',12000,6000,3000);


	function test_analyse_pattern() {
		$a_1 = $this->optimiser->analyse($this->simple_item1);
		$this->assertEquals(  $this->analysis_1, $a_1, "Item 1 analysis not as expected" );
		$a_2 = $this->optimiser->analyse($this->simple_item2);
		$this->assertEquals(  $this->analysis_2, $a_2, "Item 2 analysis not as expected" );
		$a_3 = $this->optimiser->analyse($this->simple_item3);
		$this->assertEquals(  $this->analysis_3, $a_3, "Item 3 analysis not as expected" );
	}
	

	function test_makeOptItem() {
		$o1 = new OptItem($this->optimiser,$this->simple_item1);
		$o2 = new OptItem($this->optimiser,$this->simple_item2);
		$o3 = new OptItem($this->optimiser,$this->simple_item3);
		$willbind = OptItem::makeBindsWillBind();
		$expected_freevars_1 = array( 'o2'=>$willbind );
		$expected_freevars_2 = array( 's'=>$willbind, 'o2'=>$willbind );
		$expected_freevars_3 = array( 's'=>$willbind, 'p'=>$willbind,'o'=>$willbind );
		$this->assertEquals(  $this->simple_item1, $o1->rawItem, "OptItem 1 item not as expected" );
		$this->assertEquals(  $expected_freevars_1, $o1->freeVarInfo, "OptItem 1 freevars not as expected" );
		$this->assertEquals(  $this->simple_item2, $o2->rawItem, "OptItem 2 item not as expected" );
		$this->assertEquals(  $expected_freevars_2, $o2->freeVarInfo, "OptItem 2 freevars not as expected" );
		$this->assertEquals(  $this->simple_item3, $o3->rawItem, "OptItem 3 item not as expected" );
		$this->assertEquals(  $expected_freevars_3, $o3->freeVarInfo, "OptItem 3 freevars not as expected" );
	}

	function test_itemBoundVarStats() {
		$bindings_empty = array();
		$bindings_s = array( 's'=>array( 'kind' => 'IS_BOUND') );
		$bindings_o2 = array( 'o2'=>array( 'kind' => 'IS_BOUND') );
		$bindings_both = array( 's'=>array( 'kind' => 'IS_BOUND'), 'o2'=>array( 'kind' => 'IS_BOUND') );
		
		$this->do_test_itemBoundVarStats($this->simple_item1,$this->stats_1,$bindings_empty,"item 1 - no binding");
		$this->do_test_itemBoundVarStats($this->simple_item1,$this->stats_1,$bindings_s,"item 1 - s bound");
		$this->do_test_itemBoundVarStats($this->simple_item1,$this->stats_ground_triple,$bindings_o2,"item 1 - o2 bound");
		$this->do_test_itemBoundVarStats($this->simple_item1,$this->stats_ground_triple,$bindings_both,"item 1 - both bound");

		$this->do_test_itemBoundVarStats($this->simple_item2,$this->stats_2,$bindings_empty,"item 2 - no binding");
		$this->do_test_itemBoundVarStats($this->simple_item2,$this->stats_2_s_bound,$bindings_s,"item 2 - s bound");
		$this->do_test_itemBoundVarStats($this->simple_item2,$this->stats_2_o_bound,$bindings_o2,"item 2 - o2 bound");
		$this->do_test_itemBoundVarStats($this->simple_item2,$this->stats_ground_triple,$bindings_both,"item 2 - both bound");

		$this->do_test_itemBoundVarStats($this->simple_item3,$this->stats_3,$bindings_empty,"item 3 - no binding");
		$this->do_test_itemBoundVarStats($this->simple_item3,$this->stats_3_s_bound,$bindings_s,"item 3 - s bound");
		$this->do_test_itemBoundVarStats($this->simple_item3,$this->stats_3,$bindings_o2,"item 3 - o2 bound");
		$this->do_test_itemBoundVarStats($this->simple_item3,$this->stats_3_s_bound,$bindings_both,"item 3 - both bound");

	}
	
	//;
	
	function do_test_itemBoundVarStats($item,$stats_expected,$bindings,$name) {
		$o = new OptItem($this->optimiser,$item);
		$stats = OptItem::itemBoundVarStats($o,$bindings);
		$this->assertEquals(  $stats_expected, $stats, "itemBoundVarStats $name not as expected" );
	}
	
	function test_simple_query() {
		$queryInfo = $this->examplequeries->makeSimpleQuery();
		$optimised = $this->optimiser->optimise($queryInfo);

		$this->assertEquals(  'OK', $this->optimiser->status, "optimising failed: ". implode("\n    ",$this->optimiser->reason) );

		$expected_triples = array( 'type'=>'triples', 'patterns'=>array(
								array( 'type'=>'triple', 's'=>'o2', 's_type'=>'var', 
								       'p'=>'http://example.com/hasName', 'p_type'=>'uri',
									   'o'=>'fred', 'o_type'=>'literal', 'o_datatype'=>'', 'o_lang'=>'' ),
								array( 'type'=>'triple', 's'=>'s', 's_type'=>'var', 
								       'p'=>'http://example.com/hasParent', 'p_type'=>'uri',
									   'o'=>'o2', 'o_type'=>'var', 'o_datatype'=>'', 'o_lang'=>'' ),
								array( 'type'=>'triple', 's'=>'s', 's_type'=>'var', 
								       'p'=>'p', 'p_type'=>'var',
									   'o'=>'o', 'o_type'=>'var', 'o_datatype'=>'', 'o_lang'=>'' )
							) );
							
		$actual_triples = $optimised['query']['pattern']['patterns'][0];
		$cmp = array_compare( $expected_triples, $actual_triples );
		$this->assertEquals(  array_compare( $expected_triples, $actual_triples ), 0,
								"query triples don't match: "
								. sc_get_report()
								. " :\n" . print_r($actual_triples,true)
								 );
	}
	
	function test_typical_query() {
		$queryInfo = $this->examplequeries->makeTypicalQueryNeedingToKnowClassLessRestrictiveThanPredicate();
		$optimised = $this->optimiser->optimise($queryInfo);

		$this->assertEquals(  'OK', $this->optimiser->status, "optimising failed: ". implode("\n    ",$this->optimiser->reason) );

		$expected_triples = array( 'type'=>'triples', 'patterns'=>array(
								array( 'type'=>'triple', 's'=>'subjectUri', 's_type'=>'var', 
								       'p'=>'http://www.w3.org/2004/02/skos/core#inScheme', 'p_type'=>'uri',
									   'o'=>'http://talisaspire.com/subjects/', 'o_type'=>'uri', 'o_datatype'=>'', 'o_lang'=>'' ),
								array( 'type'=>'triple', 's'=>'subjectUri', 's_type'=>'var', 
								       'p'=>'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'p_type'=>'uri',
									   'o'=>'http://www.w3.org/2004/02/skos/core#Concept', 'o_type'=>'uri', 'o_datatype'=>'', 'o_lang'=>'' ),
								array( 'type'=>'triple', 's'=>'subjectUri', 's_type'=>'var', 
								       'p'=>'http://www.w3.org/2004/02/skos/core#prefLabel', 'p_type'=>'uri',
									   'o'=>'subjectName', 'o_type'=>'var', 'o_datatype'=>'', 'o_lang'=>'' )
							) );
							
		$actual_triples = $optimised['query']['pattern']['patterns'][0];
		$cmp = array_compare( $expected_triples, $actual_triples );
		$this->assertEquals(  array_compare( $expected_triples, $actual_triples ), 0,
								"query triples don't match: "
								. sc_get_report()
								. " :\n" . print_r($actual_triples,true)
								 );
								
		$pp_optimised = $this->prettyPrintQuery($optimised);
		$this->assertEquals($pp_optimised,$this->examplequeries->optimisedTypicalQueryNeedingToKnowClassLessRestrictiveThanPredicate,
						"pretty printed query not as expected");
	}

	function test_real_queries() {
		foreach( $this->examplequeries->real_queries as $rq) {
			list($query,$opt_query,$description) = $rq;
			//echo "list($query,$opt_query,$description)\n";
			$queryInfo = $this->examplequeries->makeQuery($query);
			if ( $queryInfo ) {
				$optimised = $this->optimiser->optimise($queryInfo);
				$pp_optimised = $this->prettyPrintQuery($optimised);
				$this->assertEquals($opt_query,$pp_optimised,
								"optimised query not as expected: $description");
			} else {
				// $query not yet able to be parsed
			}
		}
	}
	
	
	function prettyPrintQuery($queryInfo) {
		$pp = new PrettyPrintQuery($queryInfo);
		return $pp->getQuery();
	}

}


?>